import fg from 'fast-glob';
import fs from "fs-extra";
import path from "path";
import { TaskResult } from "../../common/tasks/BastTask.js";
import { toolchain } from "../../toolchain.js";
import { Mangler } from "../../tools/Mangler.js";
import { appendMd5 } from "../../tools/vendor.js";
import { WebGLEnv } from "../WebGLEnv.js";
import { WebGLProcessJsTask } from "./WebGLPostBuildUnityTask.js";

export class WebGLPostBuildBrowserTask extends WebGLProcessJsTask {
    async run(): Promise<TaskResult<void>> {
        const exportRoot = path.join(toolchain.params.workSpacePath, WebGLEnv.ExportbrowserRoot);
        // 先清理旧的js，因可能不重新打包，这样老的js会留着，这里先删掉
        const oldJss = await fg('*.js', { cwd: exportRoot });
        for (const js of oldJss) {
            await fs.unlink(path.join(exportRoot, js));
        }

        // 处理JS
        await this.processJSFiles(WebGLEnv.ExportbrowserRoot, 'buildForBrowser');
        
        // mangle js files
        const reservedFile = path.join(toolchain.params.workSpacePath, WebGLEnv.MangleReserved);
        const cacheFile = path.join(toolchain.params.workSpacePath, WebGLEnv.MangleCache);
        // 先提取reserved字段
        const reserved = await toolchain.filterPuertsReserved.readAll(toolchain.params.workSpacePath, reservedFile);
        const mangler = new Mangler();
        // Brower版本puerts将所有js文件以map形式保存，terser无法获取类之间的关联，故不能混淆properties
        await mangler.beginMangle(reserved, cacheFile, true, false);
        const puertsRuntimeJs = path.join(exportRoot, WebGLEnv.PuertsRuntimeJs);
        const puertsBrowserJsResourcesJs = path.join(exportRoot, WebGLEnv.PuertsBrowserJsResourcesJs);
        // 先备份
        await fs.copy(puertsRuntimeJs, path.join(toolchain.params.workSpacePath, WebGLEnv.MangleBak, WebGLEnv.PuertsRuntimeJs));
        await fs.copy(puertsBrowserJsResourcesJs, path.join(toolchain.params.workSpacePath, WebGLEnv.MangleBak, WebGLEnv.PuertsBrowserJsResourcesJs));
        await mangler.mangleOneJs(puertsRuntimeJs, puertsRuntimeJs);
        await mangler.mangleOneJs(puertsBrowserJsResourcesJs, puertsBrowserJsResourcesJs);
        await mangler.patchJs(puertsBrowserJsResourcesJs);
        await mangler.endMangle(cacheFile);

        // 计算js文件md5
        const puertsRuntimeJsMd5 = await appendMd5(puertsRuntimeJs, WebGLEnv.MD5Len);
        const puertsBrowserJsResourcesJsMd5 = await appendMd5(puertsBrowserJsResourcesJs, WebGLEnv.MD5Len);

        // 更新版本号
        const indexHtml = path.join(exportRoot, WebGLEnv.indexHtml);
        const html = await fs.readFile(indexHtml, 'utf-8');
        let newHtml = this.replaceFileMd5(html, WebGLEnv.PuertsRuntimeJs, puertsRuntimeJsMd5.newFileName);
        newHtml = this.replaceFileMd5(newHtml, WebGLEnv.PuertsBrowserJsResourcesJs, puertsBrowserJsResourcesJsMd5.newFileName);
        
        const buildRoot = path.join(exportRoot, WebGLEnv.ExportBuildRoot);   
        // 计算export文件md5
        const outFiles = await fg('*.*', { cwd: buildRoot, onlyFiles: true });
        // 2019还需更新json文件
        const exportJson = outFiles.find(v => v.endsWith('.json'));
        let newExportJsonContent = '';
        if (exportJson) {
            newExportJsonContent = await fs.readFile(exportJson, 'utf-8');
        } else if (toolchain.params.unityVer.includes('2019')) {
            throw new Error('could not find exported json!');
        }

        for (const f of outFiles) {
            const dot = f.indexOf('.');
            const md5Info = await appendMd5(path.join(buildRoot, f), WebGLEnv.MD5Len, f.substring(dot));
            // 更新index.html
            newHtml = this.replaceFileMd5(newHtml, f, md5Info!.newFileName);
            // 更新export_browser.json
            newExportJsonContent = this.replaceFileMd5(newExportJsonContent, f, md5Info!.newFileName);
        }

        if (exportJson) {
            await fs.writeFile(exportJson, newExportJsonContent, 'utf-8');
        }

        // 生成页面，此页面不附带FyGameConfig
        await fs.writeFile(indexHtml, newHtml, 'utf-8');
        // 为所有gameid生成页面
        const allCfgs = toolchain.platformCfgParser.getAll()!;
        for (const c of allCfgs) {
            // 写入FyGameConfig
            const fgc = {
                remoteResUrl: c.url,
                gameid: c.gameid, 
                plat: c.plat, 
                apkpath: c.path,
                bundleId: c.bundleId, 
                productName: c.productName
            };
            const ss = '<script src="./puerts-runtime_';
            const spHtml = newHtml.replace(ss, '<script>window.FyGameConfig = ' + JSON.stringify(fgc) + '</script>\n' + ss);
            // 为防止有多个不同id的页面互相覆盖，再生成一个带gameid的页面
            await fs.writeFile(indexHtml.replace('.html', c.gameid + '.html'), spHtml, 'utf-8');
        }

        // 拷贝到tomcat
        const dst = WebGLEnv.SvrBrowserRoot;
        const svrPath = path.join(toolchain.params.uploadPath, dst);
        await fs.copy(exportRoot, svrPath, { recursive: true });
        // 同时拷贝到profiler目录
        await fs.copy(exportRoot, svrPath + '_profiler', { recursive: true });

        return { success: true, errorCode: 0, data: void 0 };
    }

    private async tryAppendMd5(fileName: string, dir: string, allowNotExists: boolean): Promise<{ fileName: string, newFileName: string } | null> {
        const ext = this.maxExtname(fileName);
        const file = path.join(dir, fileName);
        if (fs.existsSync(file)) {
            const md5Info = await appendMd5(file, WebGLEnv.MD5Len, ext);
            return { fileName, newFileName: md5Info.newFileName };
        }
        // 再看看是否已经加过md5了，比如可能是上次打包的，本次没有打包
        const basename = path.basename(fileName, ext);
        const out = await fg(basename + '_*' + ext, { cwd: dir });
        if (out.length > 0) {
            return { fileName, newFileName: out[0] };
        }
        // 再检查不带.gz的，如果开启了DevelopmentBuild+AutoConnectProfiler，导出的文件是没有gz的
        if (fileName.endsWith('.gz')) {
            console.error('try append md5 failed, try without .gz:', fileName);
            return await this.tryAppendMd5(fileName.replace('.gz', ''), dir, allowNotExists);
        }

        if (allowNotExists) {
            return null;
        }
        console.error('try append md5 failed:', fileName);
        process.exit(1);
    }

    private maxExtname(fileName: string): string {
        return fileName.substring(fileName.indexOf('.'));
    }

    private replaceFileMd5(content: string, fileName: string, newFileName: string): string {
        const ext = this.maxExtname(fileName);
        const basename = path.basename(fileName, ext);
        return content.replace(new RegExp(`${basename}.*${ext.replaceAll('.', '\\.')}`), newFileName);
    }
}
